package com.nexgo.payment.trans.setting.terminalkey.bccdownkey;


import android.support.annotation.NonNull;
import com.nexgo.common.ByteUtils;
import com.nexgo.common.Des;
import com.nexgo.oaf.apiv3.SdkResult;
import com.nexgo.oaf.apiv3.device.pinpad.AlgorithmModeEnum;
import com.nexgo.oaf.apiv3.device.pinpad.PinPad;
import com.nexgo.oaf.apiv3.device.pinpad.WorkKeyTypeEnum;
import com.nexgo.oaf.apiv3.device.serialport.SerialCfgEntity;
import com.nexgo.oaf.apiv3.device.serialport.SerialPortDriver;
import com.nexgo.payment.api.ApiDeviceEngine;
import com.nexgo.payment.constrant.PreferencesConstants;
import com.nexgo.payment.databases.DataSource;
import com.nexgo.payment.util.ByteUtil;
import com.nexgo.payment.util.StringUtils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * 创建日期:2018/1/10 11:07
 * 创建人： wzp
 * @version
 * 修改人： wzp
 * 类说明;
 */
public class BCCLoadTmk {
    private Logger log = LoggerFactory.getLogger("LoadTmk");
    private PinPad mPinPad;
    private final DataSource mRepository;
    private SerialPortDriver mSerialPortDriver;
    private int Keyindex;

    public BCCLoadTmk(@NonNull DataSource repository){
        mRepository = checkNotNull(repository);
        mPinPad = ApiDeviceEngine.getInstance().getPinPad();
        Keyindex = Integer.parseInt(StringUtils.firstNonEmpty(mRepository.getParamValue(PreferencesConstants.MASTER_KEY_INDEX),"0"));
        if ("2".equals(mRepository.getParamValue(PreferencesConstants.MASTER_KEY_ALGORITHM))){
            mPinPad.setAlgorithmMode(AlgorithmModeEnum.SM4);
        }
    }

    public int loadKeyByCom(int portNo, int timeout){
        int ret = requestBccTmk(portNo,timeout);
        return ret;
    }
    public void cancelLoadKey(){
        quitUpdateTmk(mSerialPortDriver);
        return;
    }
    private int parseTmkMsg(byte [] data, int len){
        int offset = 0;
        byte lrc = 0;
        byte desType[] = new byte[1];
        byte tmp[] = new byte[16];
        byte plain[];
        byte tmk[];
        byte mak[];
        byte tpk[];
        byte[] tmpTMK = new byte[16];
        byte[] tmpTPK = new byte[16];

        log.error("Enter parseTmkMsg");
        if(len < 16){
            log.debug("Msg len err ：{}", len);
            return SdkResult.Fail;
        }

        if(data[offset] != 0x02){
            log.debug("Msg header err：{} {}", data[0]);
            return SdkResult.Fail;
        }

        if(data[len-2] != 0x03){
            log.debug("Msg end err：{} ", data[len-2]);
            return SdkResult.Fail;
        }

        offset++;
        for(int i = offset; i < len-1; i++){
            lrc ^= data[i];
        }
        if(lrc != data[len-1]){
            log.debug("Msg lrc err：calc{} recv{}", lrc, data[len-1]);
            return SdkResult.Fail;
        }
        offset += 2;//跳过BCD 表示的长度，明明是65写成60，靠
        if(data[offset] != 0x30 || data[offset+1] != 0x30){
            log.debug("Msg context header err：{} {}", data[0], data[1]);
            return SdkResult.Fail;
        }
        offset += 2;
        log.error("parseTmkMsg 111111111111111111111111111111");
        byte msgVoucherNo[] = new byte[6];
        byte voucherNo[] = mRepository.getParamValue(PreferencesConstants.TRACE_NUMBER).getBytes();
        System.arraycopy(data, offset, msgVoucherNo, 0, 6);
        offset += 6;
        if(!Arrays.equals(voucherNo, msgVoucherNo)){
            log.debug("Msg voucherNo err{} recv {}", new String(voucherNo), new String(msgVoucherNo));
            return SdkResult.Fail;
        }

        byte msgTermNo[] = new byte[8];
        byte TermNo[] = mRepository.getParamValue(PreferencesConstants.TERMINAL_ID).getBytes();;
        System.arraycopy(data, offset, msgTermNo, 0, 8);
        offset += 8;
        if(!Arrays.equals(TermNo, msgTermNo)){
            log.debug("Msg terminalNo err{} recv {}", new String(TermNo), new String(msgTermNo));
            return SdkResult.Fail;
        }
        log.debug("parseTmkMsg 22222222222222222222222222222222");
        if(len == 40){
            desType[0] = '0';
            tmk = new byte[8];
            mak = new byte[8];
            tpk = new byte[8];
            System.arraycopy(data, offset, tmk, 0, 8);
            offset += 8;
            System.arraycopy(data, offset, tpk, 0, 8);
            offset += 8;
            System.arraycopy(data, offset, mak, 0, 8);

            if(!updateBccMkey(tmk, tmk.length)) {
                log.debug("des update tmk err");
                return SdkResult.Fail;
            }
            if(!updateBccWkeyNoCKV(WorkKeyTypeEnum.MACKEY, mak, mak.length)){
                log.debug("des update mak err");
                return SdkResult.Fail;
            }

            if(!updateBccWkeyNoCKV(WorkKeyTypeEnum.PINKEY, tpk, tpk.length)){
                log.debug("des update tpk err");
                return SdkResult.Fail;
            }
        }else{
            desType[0] = '6';
            String space8 = "        ";
            mak = new byte[8];
            byte[] tmkCheckValue = new byte[3];
            byte[] makCheckValue = new byte[3];
            byte[] tpkCheckValue = new byte[3];

            System.arraycopy(data, offset, tmpTMK, 0, 16);
            offset += 16;
            System.arraycopy(data, offset, tmkCheckValue, 0, 3);
            offset += 3;
            System.arraycopy(data, offset, tmpTPK, 0, 16);
            offset += 16;
            System.arraycopy(data, offset, tpkCheckValue, 0, 3);
            offset += 3;
            System.arraycopy(data, offset, mak, 0, 8);
            offset += 8;
            System.arraycopy(data, offset, makCheckValue, 0, 3);

            if(byteArrayCompare(tmpTMK, 8, space8.getBytes(), 0, 8) && byteArrayCompare(tmpTPK, 8, space8.getBytes(), 0, 8)){
                desType[0] = '0';
            }
            if( desType[0] =='0'){
                tmk = new byte[8];
                tpk = new byte[8];
                System.arraycopy(tmpTMK, 0, tmk, 0, 8);
                System.arraycopy(tmpTPK, 0, tpk, 0, 8);
            }
            else{
                tmk = new byte[16];
                tpk = new byte[16];
                System.arraycopy(tmpTMK, 0, tmk, 0, 16);
                System.arraycopy(tmpTPK, 0, tpk, 0, 16);
            }
            log.debug("hex tmpTMK {}  === {}", new String(TermNo), ByteUtils.byteArray2HexString(tmpTMK));
            log.debug("hex tmpTPK {}  === {}", new String(TermNo), ByteUtils.byteArray2HexString(tmpTPK));
            log.debug("hex tmk 密文{}  === {}", new String(TermNo), ByteUtils.byteArray2HexString(tmk));
            log.debug("hex tpk === {}", ByteUtils.byteArray2HexString(tpk));
            plain = Des.des_decrypt(TermNo,tmk);
            log.debug("hex tmk {} {}",plain.length, ByteUtils.byteArray2HexString(plain));
            if(plain.length == 16) //双倍秘钥
                System.arraycopy(plain, 0, tmk, 0, 16);
            else //单倍秘钥
                System.arraycopy(plain, 0, tmk, 0, 8);
            if(!checkBccKey(tmkCheckValue, 3, tmk, tmk.length)){
                log.debug("recv tmk err");
                return SdkResult.Fail;
            }
            if(!updateBccMkey(tmk, tmk.length)) {
                log.debug("update tmk err");
                return SdkResult.Fail;
            }
            if(!updateBccWkey(WorkKeyTypeEnum.MACKEY, mak, mak.length, makCheckValue)){
                log.debug("update mak err");
                return SdkResult.Fail;
            }

            if(!updateBccWkey(WorkKeyTypeEnum.PINKEY, tpk, tpk.length, tpkCheckValue)){
                log.debug("update tpk err");
                return SdkResult.Fail;
            }
        }
        log.error("parseTmkMsg 3333333333333333333333333333333333");
        //tradeTlv.setTagValue(TradeTlv.EncryType, desType);
        log.error("parseTmkMsg 44444444444444444444444444444");
        return 0;
    }
    private int startShowNew(final SerialPortDriver serialPortDriver , final int timeout ) {
        int ret = dlStart(serialPortDriver,timeout);
        return ret;
    }

    private boolean updateBccMkey(byte[] data, int len){
        int result = -1;
        log.error("updateBccMkey {}   {}", ByteUtils.byteArray2HexString(data), len);
        if((len != 8) && (len != 16)){
            return false;
        }
        log.error("updateBccMkey mKeyIdx {}", Keyindex);
        try{
            result = mPinPad.writeMKey(Keyindex, data, len);
            log.debug("write tmk result {}",result);
        }catch (Exception e){
            e.printStackTrace();
        }
        log.error("Out updateBccMkey");
        if( result != SdkResult.Success )
            return false;
        return true;
    }

    private boolean updateBccWkeyNoCKV(WorkKeyTypeEnum wkeyType, byte[] data, int len){
        int ret;
        if(len != 8){
            return false;
        }
        try{
            ret = mPinPad.writeWKey(Keyindex, wkeyType, data, len);
            if(ret != SdkResult.Success){
                log.debug("Msg update work key{} err {}", wkeyType, ret);
                return false;
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return true;
    }

    private boolean updateBccWkey(WorkKeyTypeEnum wkeyType, byte[] data, int len, byte[] checkValue){
        int ret;
        byte[] calcCheckValue;

        if((len != 8) && (len != 16)){
            return false;
        }
        try{
            ret = mPinPad.writeWKey(Keyindex, wkeyType, data, len);
            if(ret != SdkResult.Success){
                log.debug("Msg update work key{} err {}", wkeyType, ret);
                return false;
            }
            calcCheckValue = mPinPad.calcWKeyKCV(Keyindex, wkeyType);
            log.debug("KCV {}", ByteUtils.byteArray2HexString(calcCheckValue));
            log.debug("mkeyindex {}",Keyindex);

            log.debug("hex calcCheckValue {}  === {}", ByteUtils.byteArray2HexString(calcCheckValue));
            log.debug("hex checkValue {}  === {}", ByteUtils.byteArray2HexString(checkValue));
            if(ByteUtil.byteArrayCompare(calcCheckValue,checkValue,3)){
//            if (Arrays.equals(calcCheckValue, checkValue)){
                log.debug("工作密码校验成功");
                return true;
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return false;
    }

    private boolean checkBccKey(byte[] checkValue, int checkValueLen, byte[] key, int keyLen){
        byte[] data = new byte[8];
        byte[] calcCheckValue;

        if(checkValueLen > 8)
            return false;

        Arrays.fill(data, (byte)0x00);
        if(keyLen == 8)
            calcCheckValue = Des.des_crypt(key, data);
        else if(keyLen == 16)
            calcCheckValue = Des.trides_crypt(key, data);
        else
            return false;
        log.debug("check value {}", ByteUtils.byteArray2HexString(checkValue));
        log.debug("calc check value {}", ByteUtils.byteArray2HexString(calcCheckValue));
        return byteArrayCompare(calcCheckValue, 0, checkValue, 0, checkValueLen);
    }

    private byte[] formBccRequestMsg(){
        log.debug("Bcc form questBccTmk");
        int offset = 0;
        byte lrc = 0x00;
        byte[] msg = new byte[19];
        byte[] tmp ;
        msg[offset++] = 0x02;
        msg[offset++] = 0x00;
        msg[offset++] = 0x14;
        tmp = mRepository.getParamValue(PreferencesConstants.TRACE_NUMBER).getBytes();
        System.arraycopy(tmp, 0, msg, offset, 6);
        offset += 6;
        tmp = mRepository.getParamValue(PreferencesConstants.TERMINAL_ID).getBytes();
        System.arraycopy(tmp, 0, msg, offset, 8);
        offset += 8;
        msg[offset++] = 0x03;
        for(int i = 1; i < offset; i++){
            lrc ^= msg[i];
        }
        msg[offset] = lrc;
        log.debug("Bcc out form questBccTmk");
        log.debug("Bcc form questBccTmk {}", msg.length);
        log.debug("Bcc form questBccTmk {}", ByteUtils.byteArray2HexString(msg));
        return msg;
    }

    public int requestBccTmk(int portNo, int timeout){
        log.debug("Bcc enter questBccTmk");
        int ret = -1;
        SerialCfgEntity cfgEntity = new SerialCfgEntity();
        mSerialPortDriver = ApiDeviceEngine.getInstance().getSerialPortDriver(portNo);
        try{
            mSerialPortDriver.disconnect();
            cfgEntity.setBaudRate(9600);
            cfgEntity.setParity('n');
            cfgEntity.setDataBits(8);
            cfgEntity.setStopBits(1);
            ret = mSerialPortDriver.connect(cfgEntity);
            if(ret != SdkResult.Success  ){
                log.debug(" connect err{} ", ret);
                return ret;
            }
            ret = startShowNew(mSerialPortDriver , timeout);
            }catch (Exception e){
                e.printStackTrace();
            }
            log.error("Bcc out questBccTmk");
            return ret ;
        }

    int dlStart(SerialPortDriver serialPortDriver , int timeout){

        int ret;
        byte[] tmp;
        byte[] buf = new byte[256];
        byte[] recvDate = new byte[256];
        int totalLen = 0;
        int recvLen = 0;


        tmp = formBccRequestMsg();
        log.debug("Bcc send requestBccTmk");
        try{
        ret = serialPortDriver.send(tmp, tmp.length);

        log.debug("Bcc send status {}", ret);

        while(true) {
            ret = serialPortDriver.recv(buf, buf.length, timeout*1000);
            if (ret < 0) {
                log.debug("recv err {}", ret);
                quitUpdateTmk(serialPortDriver);
                return ret;
            } else {
                if (recvLen == 0) {   //第一次收到数据
                    if (buf[recvLen] != 0x02) {
                        log.debug("recv data error");
                        return ret;
                    }
                    byte lenDate[] = new byte[2];
                    System.arraycopy(buf, 1, lenDate, 0, 2);  //跳过第一个字节 0x02
                    totalLen = ByteUtils.bcdByteArray2Int(lenDate);
                    log.debug("recv form PC totalLen {}", totalLen);
                }
                System.arraycopy(buf, 0, recvDate, recvLen, ret);
                recvLen += ret;
                log.debug("recv form PC offSet {}", recvLen);
                log.debug("recv form PC buf{}", ByteUtils.byteArray2HexString(buf));
            }
            if (recvLen >= totalLen + 5) {
                log.debug("recv form PC len {}", recvLen);
                log.debug("recv form PC recvDate {}", ByteUtils.byteArray2HexString(recvDate));
                ret = parseTmkMsg(recvDate, recvLen);
                if (ret != 0) {
                    log.debug(" parse err{} ", ret);
                    quitUpdateTmk(serialPortDriver);
                    return ret;
                }
                quitUpdateTmk(serialPortDriver);
                return ret;
            }
        }
        }catch (Exception e){
            e.printStackTrace();
        }
        log.debug("Bcc out questBccTmk");
        return SdkResult.Success;
    }

    private void quitUpdateTmk( SerialPortDriver serialPortDriver)  {
        try {
            serialPortDriver.clrBuffer();
            serialPortDriver.disconnect();
        }catch (Exception e ){
            e.printStackTrace();
        }
    }

    public static boolean byteArrayCompare(byte[] data1, int data1Pos, byte[] data2, int data2Pos, int len) {
        for (int i = 0; i < len; i++) {
            if (data1[data1Pos + i] != data2[data2Pos + i])
                return false;
        }
        return true;
    }
}
